﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace UniSender
{
    using IExpando = IDictionary<string, object>;

    /// <summary>
    /// Допустимые языки
    /// </summary>
    public class LANG
    {
        public const string
            RU = "ru",
            EN = "en",
            IT = "it";
    }

    /// <summary>
    /// Клиент для работы с UniSender API
    /// </summary>
    public partial class Client
    {
        /// <summary>
        /// Язык
        /// </summary>
        public virtual string Lang { get; protected set; }

        /// <summary>
        /// Ключ клиента
        /// </summary>
        public virtual string Key { get; protected set; }

        /// <summary>
        /// Тестовый режим
        /// </summary>
        public virtual bool TestMode { get; set; }

        /// <summary>
        /// Использовать GET-запрос вместо POST
        /// </summary>
        public virtual bool UseGet { get; set; }

        /// <summary>
        /// Не выбрасывать исключения из специфичных методов
        /// </summary>
        public virtual bool MuteExceptions { get; set; }

        public virtual IDeserializer Deserializer { get; set; }

        #region Events

        /// <summary>
        /// Начало запроса
        /// </summary>
        public event Action<UniSenderEventArgs> OnRequestStart;

        /// <summary>
        /// Ответ сервера получен и успешно разобран
        /// </summary>
        public event Action<UniSenderRequestSuccessEventArgs> OnRequestSuccess;

        /// <summary>
        /// Возникла ошибка
        /// </summary>
        public event Action<UniSenderExceptionEventArgs> OnException;

        /// <summary>
        /// Сервер прислал ответ с пердупреждениями
        /// </summary>
        public event Action<UniSenderWarningEventArgs> OnWarning;

        /// <summary>
        /// Сервер прислал ошибку в ответе
        /// </summary>
        public event Action<UniSenderErrorEventArgs> OnError;

        #endregion

        #region Constructor

        /// <summary>
        /// Инициализация клиента
        /// </summary>
        /// <param name="lang">Язык</param>
        /// <param name="key">Ключ клиента</param>
        public Client(string lang, string key)
        {
            Deserializer = new Deserializer();

            MuteExceptions = true;
            TestMode = false;
            UseGet = false;
            Lang = lang;
            Key = key;
        }

        /// <summary>
        /// Инициализация клиента с языком по умолчанию ("ru")
        /// </summary>
        /// <param name="key">Ключ клиента</param>
        public Client(string key) : this(LANG.RU, key) { }

        #endregion

        #region Service Methods

        /// <summary>
        /// Выполнение запроса к серверу
        /// </summary>
        /// <param name="method">Имя метода</param>
        /// <param name="args">Параметры</param>
        /// <returns>Текст ответа</returns>
        protected virtual string Request(string method, IExpando args)
        {
            return new UniSenderRequest(Lang, method, Key, args).Send(UseGet);
        }

        /// <summary>
        /// Выполнени запроса к серверу
        /// </summary>
        /// <typeparam name="T">Тип рузультат</typeparam>
        /// <param name="method">Имя метода</param>
        /// <param name="args">Параметры</param>
        /// <returns>Объектное представление ответа сервиса</returns>
        /// <exception cref="UniSender.UniSenderTransportException">Выбрасывается, когда возникает ошибка при формировании и отправке запроса</exception>
        /// <exception cref="UniSender.UniSenderParseException">Выбрасывается, когда возникает ошибка при разборе ответа сервера</exception>
        /// <exception cref="UniSender.UniSenderErrorException">Выбразывается, когда сервер вернул код ошибки</exception>
        protected virtual Native.Response<T> Request<T>(string method, IExpando args)
            where T : class
        {
            string responseText;
            Native.Response<object> testResponse;
            Native.Response<T> response;

            if (OnRequestStart != null)
                OnRequestStart(new UniSenderEventArgs()
                {
                    Lang = Lang,
                    Method = method,
                    Args = args,
                });

            try
            {
                responseText = Request(method, args);
            }
            catch (UniSenderTransportException)
            {
                throw;
            }
            catch (Exception exc)
            {
                throw new UniSenderTransportException("Произошла непредвиденная ошибка при отправке запроса", exc)
                {
                    Lang = Lang,
                    Method = method,
                    Key = Key,
                    Args = args
                };
            }

            try
            {
                testResponse = Deserializer.Deserialize<Native.Response<object>>(responseText);
            }
            catch (Exception exc)
            {
                throw new UniSenderParseException("Произошла ошибка при разборе ответа сервера", exc)
                {
                    Lang = Lang,
                    Method = method,
                    Args = args,
                    ResponseText = responseText,
                    ExpectedType = typeof(T)
                };
            }

            if (OnRequestSuccess != null)
                OnRequestSuccess(new UniSenderRequestSuccessEventArgs()
                {
                    Lang = Lang,
                    Method = method,
                    Args = args,
                    ResponseText = responseText,
                    Response = testResponse
                });

            if (!string.IsNullOrEmpty(testResponse.error))
            {
                throw new UniSenderErrorException("Сервер вернул сообщение об ошибке")
                {
                    Lang = Lang,
                    Method = method,
                    Args = args,
                    Error = testResponse.error,
                    Code = testResponse.code
                };
            }

            if (OnWarning != null && testResponse.warnings != null && testResponse.warnings.Length > 0)
                OnWarning(new UniSenderWarningEventArgs()
                {
                    Lang = Lang,
                    Method = method,
                    Args = args,
                    Warnings = testResponse.warnings.Select(w => w.warning).ToArray()
                });

            try
            {
                response = Deserializer.Deserialize<Native.Response<T>>(responseText);
            }
            catch (Exception exc)
            {
                throw new UniSenderParseException("Произошла ошибка при разборе ответа сервера", exc)
                {
                    Lang = Lang,
                    Method = method,
                    Args = args,
                    ResponseText = responseText,
                    ExpectedType = typeof(T)
                };
            }

            return response;
        }

        #endregion

        #region Calling

        /// <summary>
        /// Вызов метода сервиса
        /// </summary>
        /// <typeparam name="T">Тип результата</typeparam>
        /// <param name="method">Имя метода</param>
        /// <param name="args">Параметры</param>
        /// <returns>Результат</returns>
        /// <exception cref="UniSender.UniSenderTransportException">Выбрасывается, когда возникает ошибка при формировании и отправке запроса</exception>
        /// <exception cref="UniSender.UniSenderParseException">Выбрасывается, когда возникает ошибка при разборе ответа сервера</exception>
        /// <exception cref="UniSender.UniSenderErrorException">Выбразывается, когда сервер вернул код ошибки</exception>
        public virtual T Call<T>(string method, IExpando args)
            where T : class
        {
            return Request<T>(method, args).result;
        }

        /// <summary>
        /// Вызов метода сервиса
        /// </summary>
        /// <typeparam name="T">Тип результата</typeparam>
        /// <param name="method">Имя метода</param>
        /// <returns>Результат</returns>
        /// <exception cref="UniSender.UniSenderTransportException">Выбрасывается, когда возникает ошибка при формировании и отправке запроса</exception>
        /// <exception cref="UniSender.UniSenderParseException">Выбрасывается, когда возникает ошибка при разборе ответа сервера</exception>
        /// <exception cref="UniSender.UniSenderErrorException">Выбразывается, когда сервер вернул код ошибки</exception>
        public T Call<T>(string method)
            where T : class
        {
            return Call<T>(method, null);
        }

        /// <summary>
        /// Вызов метода сервиса, все исключения игнорируются
        /// </summary>
        /// <typeparam name="T">Тип результата</typeparam>
        /// <param name="method">Имя метода</param>
        /// <param name="args">Параметры</param>
        /// <returns>Результат</returns>
        /// <remarks>Исключения будут переданы в обработчики OnException, сообщение сервера об ошибке - в OnError</remarks>
        public virtual T TryCall<T>(string method, IExpando args)
            where T : class
        {
            try
            {
                return Call<T>(method, args);
            }
            catch (UniSenderErrorException error)
            {
                if (OnError != null)
                    OnError(new UniSenderErrorEventArgs()
                    {
                        Lang = Lang,
                        Method = method,
                        Args = args,
                        Error = error.Error,
                        Code = error.Code
                    });
            }
            catch (UniSenderException exc)
            {
                if (OnException != null)
                    OnException(new UniSenderExceptionEventArgs()
                    {
                        Lang = Lang,
                        Method = method,
                        Args = args,
                        Exception = exc
                    });
            }
            return null;
        }

        /// <summary>
        /// Вызов метода сервиса, все исключения игнорируются
        /// </summary>
        /// <typeparam name="T">Тип результата</typeparam>
        /// <param name="method">Имя метода</param>
        /// <returns>Результат</returns>
        /// <remarks>Исключения будут переданы в обработчики OnException, сообщение сервера об ошибке - в OnError</remarks>
        public T TryCall<T>(string method)
            where T : class
        {
            return TryCall<T>(method, null);
        }

        /// <summary>
        /// Вызов метода сервиса, игнорирование исключений регулируется свойством MuteExceptions
        /// </summary>
        /// <typeparam name="T">Тип результата</typeparam>
        /// <param name="method">Имя метода</param>
        /// <param name="args">Параметры</param>
        /// <returns>Результат</returns>
        public T Apply<T>(string method, IExpando args)
            where T : class
        {
            return MuteExceptions ? TryCall<T>(method, args) : Call<T>(method, args);
        }

        /// <summary>
        /// Вызов метода сервиса, игнорирование исключений регулируется свойством MuteExceptions
        /// </summary>
        /// <typeparam name="T">Тип результата</typeparam>
        /// <param name="method">Имя метода</param>
        /// <returns>Результат</returns>
        public T Apply<T>(string method)
            where T : class
        {
            return MuteExceptions ? TryCall<T>(method) : Call<T>(method);
        }

        /// <summary>
        /// Вызов метода сервиса, игнорирование исключений регулируется свойством MuteExceptions
        /// </summary>
        /// <param name="method">Имя метода</param>
        /// <param name="args">Параметры</param>
        public void Apply(string method, IExpando args)
        {
            Apply<object>(method, args);
        }

        #endregion
    }
}
